/*
This file is part of buco.  
Copyright (c) 2012 Christopher Pawelzik <christopher@cpawelzik.com>

Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 
*/ 
package buco.core.api;

import java.util.*;
/**
 * @author Christopher
 *
 */
public class BundleComposition {
	
	private ArrayList<Bundle> bundles;
	private ArrayList<BundleWire> wires; 
	HashSet<String> bundleSignatures; 
	
	private int bundleCounter = 1; 
	
	public BundleComposition() {
		bundles = new ArrayList<Bundle>(); 
		wires = new ArrayList<BundleWire>();
		bundleSignatures = new HashSet<String>(); 
	}
	
	public List<Bundle> getBundles() {
		return bundles;  
	}
	
	public List<BundleWire> getWires() {
		return wires; 
	}
	
	public Bundle getBundleByID(int bundleID) {
		for(Bundle bundle : this.getBundles()) {
			if(bundle.getBundleID() == bundleID) {
				return bundle; 
			}
		}
		return null; 
	}
	
	public void addBundle(Bundle bundle) throws DuplicateBundleException {
		// Get unique bundle signature 
		String signature = getBundleSignature(bundle); 
		if(bundleSignatures.contains(signature)) {
			throw new DuplicateBundleException(); 
		}
		
		// Assign a unique bundle id
		if(bundle.getBundleID() == 0) { 		
			bundle.setBundleID(++bundleCounter);
		}
		
		bundles.add(bundle);
		updateWires(bundle);
		
		// Put signature 
		bundleSignatures.add(signature);
	}
	
	private String getBundleSignature(Bundle bundle) {
		StringBuilder sb = new StringBuilder(); 
		sb.append(bundle.getBundleSymbolicName()); 
		if(bundle.getBundleVersion() != null) {
			sb.append(" ;version="); 
			sb.append(bundle.getBundleVersion().toString()); 
		}
		return sb.toString(); 
	}
	
	public void removeBundle(Bundle bundle) {
		int id = bundle.getBundleID();
		// Remove bundle from composition 
		bundles.remove(bundle); 		
		// Remove signature 
		bundleSignatures.remove(getBundleSignature(bundle)); 
		// Remove the wires that go to / get out from this bundle 
		for(int i=wires.size()-1; i>=0; i--) {
			if(wires.get(i).getFrom().getBundleID() == id ||
			   wires.get(i).getTo().getBundleID() == id) {
				wires.remove(i); 
			}
		}		
	}
	
	/**
	 * Returns a list of {@link BundleWire} instances, that go out from the given  {@link Bundle}
	 * @param Bundle The Bundle
	 * @param onlyActive True, if only activated wires should be returned. 
	 */
	public List<BundleWire> getOutgoingWires(Bundle bundle, boolean onlyActive) {
		ArrayList<BundleWire> list = new ArrayList<BundleWire>(); 
		for(BundleWire wire : wires) {
			if(wire.getFrom() != null && wire.getFrom().getBundleID() == bundle.getBundleID()) {				
				if(!onlyActive || (onlyActive && wire.isWireActive())) {
					list.add(wire);
				}
			}
		}
		return list;
	} 
	
	public List<BundleWire> getIncomingWires(Bundle bundle, boolean onlyActive) {
		ArrayList<BundleWire> list = new ArrayList<BundleWire>(); 
		for(BundleWire wire : wires) {
			if(wire.getTo() != null && wire.getTo().getBundleID() == bundle.getBundleID()) {
				if(!onlyActive || (onlyActive && wire.isWireActive())) {
					list.add(wire);
				}
			}
		}
		return list;
	}
	
	public List<SemanticMessage> checkIntegrity() {
		List<IntegrityCheck> checks = IntegrityCheckRegistry.createIntegrityChecks();
		ArrayList<SemanticMessage> warnings = new ArrayList<SemanticMessage>(); 
		
		for(IntegrityCheck check : checks) {
			check.setComposition(this); 
			for(Bundle bundle : bundles) {
				BundleIntegrityReport report = new BundleIntegrityReport(bundle); 
				check.checkIntegrity(bundle, report);
				warnings.addAll(report.getMessages()); 
			}
		}
		
		return warnings; 
	}
	
	private void updateWires(Bundle addedBundle) {
		for(Bundle bundle : bundles) {
			wire(addedBundle, bundle); 
			wire(bundle, addedBundle); 
		}
	}
	
	/**
	 * Tries to find requirements of the first bundles, that are satisfied by the other. 
	 * If there is at least one match, a wire is created. 	
	 */
	private void wire(Bundle from, Bundle to) { 
		List<Requirement> requirements = from.getRequirements();
		List<Capability> capabilities = to.getCapabilities(); 
		BundleWire wire = null; 
				
		for(Requirement req : requirements) {			
			for(Capability cap : capabilities) {
				if(req.isSatisfiedBy(to, cap)) {
					if(wire == null) { 
						wire = new BundleWire(from, to);
					}
					wire.addMatch(req, cap);					
				}
			}
		}
		
		if(wire != null) {
			this.wires.add(wire); 
		}
	}	
	
	public BundleWire getWire(Bundle from, Bundle to) {
		for(BundleWire wire : wires) {
			if(wire.getTo().getBundleID() == to.getBundleID() &&
			   wire.getFrom().getBundleID() == from.getBundleID())
				return wire; 
		}
		return null; 
	}
	
}
